package dataflow.core;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ExecutorService;

import org.apache.log4j.Logger;

import dataflowScheme.Port;
import dataflowScheme.PortSet;
import dataflowScheme.SynchroPort;


public class TaskExecutionEvent implements ExecutionEvent, Runnable {

	private Logger log = Logger.getLogger(TaskExecutionEvent.class);
	
	private final Method runMethod;
	private final Class<?> sourceClass;
	private final int portset;
	private final Task task;
	private final Map<String, Object> values;
	private final Map<String, Method> portMethods;
	
	public TaskExecutionEvent(Task task, int portset) {
		this.task = task;
		this.portset = portset;
		this.runMethod = task.getRunMethod();
		this.sourceClass = task.getSourceClass();
		this.values = task.getPortValues();
		this.portMethods = task.getPortMethods();
	}
	
	@Override
	public void execute(ExecutionEventParser executionEventParser, ExecutorService threadExecutor) {
		threadExecutor.submit(this);
	}

	@Override
	public void run() {
		int returnValue = -1;
		try{
			returnValue = invoke();
		} catch(Exception e){
			log.error("Unknown exception while processing '" + task.getPE().getName() + "'.", e);
		} finally {
			task.receiveResult(returnValue);
		}
	}
	
	private int invoke(){
		dataflowScheme.PE ePe = task.getPE();
		
		log.debug("starting executing '" + ePe.getName() + "'");
		
		PortSet ps = PE.findPortSetByID(ePe, portset);
		if(ps == null){
			log.error("Bad number of portset of '" + ePe.getName() + ".");
			return -1;
		}
	
		Object sourceInstance = createSourceInstance();
		if(sourceInstance == null){
			return -1;
		}
		
		if(!initializeSourcePorts(ps, sourceInstance, ePe)){
			return -1;
		}
			
		try{
			return (Integer)runMethod.invoke(sourceInstance, portset);
		} catch (InvocationTargetException ite){
			log.error("Invocation of main method failed.", ite);
		} catch (IllegalAccessException iae){
			log.error("Method '" + runMethod.getName() + "' have to be public.",iae);
		}
		
		return -1;
	}
	
	private boolean initializeSourcePorts(PortSet ps, Object sourceInstance, dataflowScheme.PE ePe){
		for (Port p : ps.getPort()){
			
			if (p instanceof SynchroPort){
				continue;
			}
			
			Object value = values.remove(p.getName());
			if(value == null){
				log.error("Sth wrong, can not invoke portset method because necessary value doens't exist.");
				return false;
			}
			
			Method m = portMethods.get(p.getName());
			try{
				m.invoke(sourceInstance, value);
			}catch(InvocationTargetException ite){
				log.error("Invocation of port '" + p.getName() + "' failed.", ite);
				return false;
			} catch (IllegalArgumentException e) {
				log.error("Method '" + m.getName() + "' of '" + ePe.getName() + "' has bad arguments.", e);
				return false;
			} catch (IllegalAccessException e) {
				log.error("Method '" + m.getName() + "' of '" + ePe.getName() + "' have to be public.",e);
				return false;
			}
		}
		return true;
	}

	private Object createSourceInstance(){
		Object sourceInstance = null;
		try{
			sourceInstance = sourceClass.newInstance();
			task.setSourceInstance(sourceInstance);
		} catch(IllegalAccessException ilae){
			log.error("Cannot create instance of Task", ilae);
		} catch(InstantiationException ie){
			log.error("Cannot create instance of Task", ie);
		}
		
		return sourceInstance;
	}
}
